﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TopDownOperatorPrecedence
{


    public class PNode
    {
        public string ID { get; set; }
        public string Name { get; set; }
        public string Key { get; set; }
        public string Value { get; set; }
        public string Type { get; set; }
        public string Arity { get; set; }
        public bool IsAssignment { get; set; }
        public bool IsReserved { get; set; }
        public PNode First { get; set; }
        public PNode Second { get; set; }
        public PNode Third { get; set; }
        public int LeftBindingPower { get; set; }
        public PScope Scope { get; set; }

        public override string ToString()
        {
            return this.ToString(0);
        }

        public virtual string ToString(int indent)
        {
            string indentText = new string(' ', indent);
            string propIndentText = new string(' ', indent + 4);
            return indentText + "{\r\n" + string.Join(",\r\n" + propIndentText, new string[] { 
                propIndentText+"ID = "+ this.ID.ToStringWithNull(),
                "Name = "+ this.Name.ToStringWithNull(),
                "Arity = "+ this.Arity.ToStringWithNull(),
                "Type = "+ this.Type.ToStringWithNull(),
                "Value = "+ this.Value.ToStringWithNull(),
                "Key = "+ this.Key.ToStringWithNull(),
                "IsAssignment = "+ this.IsAssignment.ToString(),
                "IsReserved = "+ this.IsReserved.ToStringWithNull(),
                "Scope = " + this.Scope.ToStringWithNull(),
                "First = "+ (this.First==null? "null":this.First.ToString(indent+4)),
                "Second = "+ (this.Second==null? "null":this.Second.ToString(indent+4)),
                "Third = "+ (this.Third==null? "null":this.Third.ToString(indent+4)),
            }) + "\r\n" + indentText + "}";
        }

        public virtual PNode DeepClone()
        {
            return new PNode
            {
                ID = this.ID,
                Name = this.Name,
                Key = this.Key,
                Value = this.Value,
                Type = this.Type,
                Arity = this.Arity,
                IsAssignment = this.IsAssignment,
                IsReserved = this.IsReserved,
                First = this.First,
                Second = this.Second,
                Third = this.Third,
                LeftBindingPower = this.LeftBindingPower,
                Scope = this.Scope,
                NullDenotationFunc = this.NullDenotationFunc,
                LeftDenotationFunc = this.LeftDenotationFunc,
                StatementDenotationFunc = this.StatementDenotationFunc,
            };
        }

        public PNode NullDenotation()
        {
            if (this.NullDenotationFunc == null)
            {
                throw new Exception("No null denotation defined");
            }

            return this.NullDenotationFunc(this);
        }

        public PNode LeftDenotation(PNode node)
        {
            if (this.LeftDenotationFunc == null)
            {
                throw new Exception("No left denotation defined");
            }

            return this.LeftDenotationFunc(this, node);
        }

        public PNode StatementDenotation()
        {
            if (this.StatementDenotationFunc == null)
            {
                throw new Exception("No statement denotation defined");
            }

            return this.StatementDenotationFunc(this);
        }

        public Func<PNode, PNode> NullDenotationFunc { get; set; }
        public Func<PNode, PNode, PNode> LeftDenotationFunc { get; set; }
        public Func<PNode, PNode> StatementDenotationFunc { get; set; }
    }

    public class PNodeArray : PNode
    {
        public List<PNode> ArrayObject { get; set; }
        public override PNode DeepClone()
        {
            return new PNodeArray
            {
                ID = this.ID,
                Name = this.Name,
                Key = this.Key,
                Type = this.Type,
                Value = this.Value,
                Arity = this.Arity,
                IsAssignment = this.IsAssignment,
                IsReserved = this.IsReserved,
                First = this.First,
                Second = this.Second,
                Third = this.Third,
                LeftBindingPower = this.LeftBindingPower,
                Scope = this.Scope,
                ArrayObject = this.ArrayObject.ToList(),
            };
        }

        public override string ToString()
        {
            return this.ToString(0);
        }

        public override string ToString(int indent)
        {
            string indentText = new string(' ', indent);
            string propIndentText = new string(' ', indent + 4);
            return indentText + "{\r\n" + string.Join(",\r\n" + propIndentText, new string[] { 
                propIndentText+"ID = "+ this.ID.ToStringWithNull(),
                "Name = "+ this.Name.ToStringWithNull(),
                "Arity = "+ this.Arity.ToStringWithNull(),
                "Type = "+ this.Type.ToStringWithNull(),
                "Key = "+ this.Key.ToStringWithNull(),
                "Value = ["+ string.Join(",\r\n", ArrayObject.Select(p=>p.ToString(indent+2)))+"]",
                "IsAssignment = "+ this.IsAssignment.ToString(),
                "IsReserved = "+ this.IsReserved.ToStringWithNull(),
                "Scope = " + this.Scope.ToStringWithNull(),
                "First = "+ (this.First==null? "null":this.First.ToString(indent+4)),
                "Second = "+ (this.Second==null? "null":this.Second.ToString(indent+4)),
                "Third = "+ (this.Third==null? "null":this.Third.ToString(indent+4)),
            }) + "\r\n" + indentText + "}";
        }
    }

    public class PScope
    {
        private Dictionary<string, PNode> defineScope;
        public PScope Parent { get; private set; }

        public PScope(PScope parent)
        {
            this.defineScope = new Dictionary<string, PNode>();
            this.Parent = parent;
        }

        public override string ToString()
        {
            return "{" + string.Join(", ", this.defineScope.Keys) + "}";
        }

        public PNode Define(PNode node)
        {
            if (this.defineScope.ContainsKey(node.Value))
            {
                PNode result = this.defineScope[node.Value];
                throw new Exception(node.Value + " has already been " + (node.IsReserved ? "reserved" : "defined"));
            }

            this.defineScope.Add(node.Value, node);
            node.IsReserved = false;
            node.NullDenotationFunc = curr => curr;
            node.LeftDenotationFunc = null;
            node.StatementDenotationFunc = null;
            node.LeftBindingPower = 0;
            node.Scope = this;
            return node;
        }

        public PNode Find(string name, Dictionary<string, PNode> symbolTable)
        {
            PScope scope = this;
            while (true)
            {
                if (scope.defineScope.ContainsKey(name))
                {
                    return scope.defineScope[name];
                }

                scope = scope.Parent;
                if (scope == null)
                {
                    if (symbolTable.ContainsKey(name))
                    {
                        return symbolTable[name];
                    }
                    else
                    {
                        return symbolTable["(name)"];
                    }
                }
            }
        }

        public void Reserve(PNode node)
        {
            if (node.Arity != "name" || node.IsReserved)
            {
                return;
            }

            if (this.defineScope.ContainsKey(node.Value))
            {
                var tmp = this.defineScope[node.Value];
                if (tmp.IsReserved)
                {
                    return;
                }

                if (tmp.Arity == "name")
                {
                    throw new Exception("Already defined " + tmp.Value);
                }
            }
            else
            {
                this.defineScope.Add(node.Value, node);
                node.IsReserved = true;
            }
        }
    }
}
